﻿package com.fatgirl.utils
{
	import flash.utils.Dictionary;

	/**
	 * To successfully store and retrieve (key->value) mapping from a HashMap.
	 * HashMap accept any type of object to be the key: number, string, Object etc...
	 * But it is only get fast accessing with string type keys. Others are slow.
	 * Dictionary的键可以是Object，而Object的键只能是String。Dictionary['key']并不能替代Dictionary[key]，即使key.toString='key'。
	 * Dictionary继承自Object，是对其的又一次封装。如果键确定是String，使用Object即可
	 * @author AS1
	 */
	public class HashMap
	{

		private var length:int;
		private var content:Dictionary;

		public function HashMap()
		{
			length=0;
			content=new Dictionary();
		}

		//-------------------public methods--------------------

		/**
		 * Returns the number of keys in this HashMap.
		 */
		public function size():int
		{
			return length;
		}

		/**
		 * Returns if this HashMap maps no keys to values.
		 */
		public function isEmpty():Boolean
		{
			return (length == 0);
		}

		/**
		 * Returns an Array of the keys in this HashMap.
		 */
		public function keys():Array
		{
			var temp:Array=new Array(length);
			var index:int=0;
			for (var i:* in content)
			{
				temp[index]=i;
				index++;
			}
			return temp;
		}

		/**
		 * Call func(key) for each key.
		 * @param func the function to call
		 */
		public function eachKey(func:Function):void
		{
			for (var i:* in content)
			{
				func(i);
			}
		}

		/**
		 * Call func(value) for each value.
		 * @param func the function to call
		 */

		public function eachValue(func:Function):void
		{
			for each (var i:* in content)
			{
				func(i);
			}
		}

		/**
		 * Returns an Array of the values in this HashMap.
		 */
		public function values():Array
		{
			var temp:Array=new Array(length);
			var index:int=0;
			for each (var i:* in content)
			{
				temp[index]=i;
				index++;
			}
			return temp;
		}

		/**
		 * Tests if some key maps into the specified value in this HashMap.
		 * This operation is more expensive than the containsKey method.
		 */
		public function containsValue(value:*):Boolean
		{
			for each (var i:* in content)
			{
				if (i === value)
				{
					return true;
				}
			}
			return false;
		}

		/**
		 * Tests if the specified object is a key in this HashMap.
		 * This operation is very fast if it is a string.
		 * @param   key   The key whose presence in this map is to be tested
		 * @return <tt>true</tt> if this map contains a mapping for the specified
		 */
		public function containsKey(key:*):Boolean
		{
			if (content[key] != undefined)
			{
				return true;
			}
			return false;
		}

		/**
		 * Returns the value to which the specified key is mapped in this HashMap.
		 * Return null if the key is not mapped to any value in this HashMap.
		 * This operation is very fast if the key is a string.
		 * @param   key the key whose associated value is to be returned.
		 * @return  the value to which this map maps the specified key, or
		 *          <tt>null</tt> if the map contains no mapping for this key
		 *           or it is null value originally.
		 */
		private function get(key:*):*
		{
			var value:*=content[key];
			if (value !== undefined)
			{
				return value;
			}
			return null;
		}

		/**
		 * Same functionity method with different name to <code>get</code>.
		 *
		 * @param   key the key whose associated value is to be returned.
		 * @return  the value to which this map maps the specified key, or
		 *          <tt>null</tt> if the map contains no mapping for this key
		 *           or it is null value originally.
		 */
		public function getValue(key:*):*
		{
			return get(key);
		}

		/**
		 * Associates the specified value with the specified key in this map.
		 * If the map previously contained a mapping for this key, the old value is replaced.
		 * If value is null, means remove the key from the map.
		 * @param key key with which the specified value is to be associated.
		 * @param value value to be associated with the specified key. null to remove the key.
		 * @return previous value associated with specified key, or <tt>null</tt>
		 *	       if there was no mapping for key.  A <tt>null</tt> return can
		 *	       also indicate that the HashMap previously associated
		 *	       <tt>null</tt> with the specified key.
		 */
		public function put(key:*, value:*):*
		{
			if (key == null)
			{
				throw new ArgumentError("cannot put a value with undefined or null key!");
				return undefined;
			}
			else if (value == null)
			{
				return remove(key);
			}
			else
			{
				var exist:Boolean=containsKey(key);
				if (!exist)
				{
					length++;
				}
				var oldValue:*=this.get(key);
				content[key]=value;
				return oldValue;
			}
		}

		/**
		 * Removes the mapping for this key from this map if present.
		 *
		 * @param  key key whose mapping is to be removed from the map.
		 * @return previous value associated with specified key, or <tt>null</tt>
		 *	       if there was no mapping for key.  A <tt>null</tt> return can
		 *	       also indicate that the map previously associated <tt>null</tt>
		 *	       with the specified key.
		 */
		public function remove(key:*):*
		{
			var exist:Boolean=containsKey(key);
			if (!exist)
			{
				return null;
			}
			var temp:*=content[key];
			delete content[key];
			length--;
			return temp;
		}

		/**
		 * Clears this HashMap so that it contains no keys no values.
		 */
		public function clear():void
		{
			length=0;
			content=new Dictionary();
		}

		/**
		 * Return a same copy of HashMap object
		 */
		public function clone():HashMap
		{
			var temp:HashMap=new HashMap();
			for (var i:* in content)
			{
				temp.put(i, content[i]);
			}
			return temp;
		}

		public function toString():String
		{
			var ks:Array=keys();
			var vs:Array=values();
			var temp:String="HashMap Content:\n";
			for (var i:int=0; i < ks.length; i++)
			{
				temp+=ks[i] + " -> " + vs[i] + "\n";
			}
			return temp;
		}
	}
}